media_pp\elements\source/rtsp_source.rs
1use std::{sync::Arc, time::Duration};
2
3use crate::pp_log::{PpLog, pp_error, pp_info};
4use ffmpeg_next as ffmpeg;
5use thiserror::Error as ThisError;
6
7use crate::{
8 buffer::MediaBuffer,
9 bus::{Bus, BusEvent},
10 control::{ControlReceiver, drain_control},
11 element::{Element, ElementType, Source, SourceElement, element_pp_log},
12 elements::RtspTransport,
13 error::Result,
14 pad::SrcPad,
15};
16
17use super::file_demuxer::StreamInfo;
18
19/// Errors specific to `RtspSource`. Converts into the crate-wide `Error`
20/// via `?` (see [`crate::error::Error`]).
21#[derive(Debug, ThisError)]
22pub enum RtspSourceError {
23 #[error("ffmpeg error: {0}")]
24 Ffmpeg(#[from] ffmpeg::Error),
25 #[error("RtspSource doesn't support seeking a live stream")]
26 SeekUnsupported,
27}
28
29/// Construction-time options for [`RtspSource::open`].
30#[derive(Debug, Clone, Copy)]
31pub struct RtspOptions {
32 pub transport: RtspTransport,
33 /// Socket I/O timeout — ffmpeg's own `timeout` RTSP demuxer option,
34 /// which covers the initial connect/handshake reads too, not just
35 /// steady-state ones. Without this, ffmpeg's own default is *no
36 /// timeout at all*, meaning [`RtspSource::open`] can hang forever
37 /// against an unreachable or dead server.
38 pub timeout: Duration,
39}
40
41impl Default for RtspOptions {
42 fn default() -> Self {
43 Self {
44 transport: RtspTransport::Tcp,
45 timeout: Duration::from_secs(5),
46 }
47 }
48}
49
50/// Demuxes a live RTSP stream — the client/receive counterpart to
51/// [`crate::elements::RtspSink`] (which publishes). One src pad per
52/// stream the server advertises, same shape as
53/// [`crate::elements::FileDemuxer`].
54///
55/// Deliberately does **not** retry or reconnect internally: a read failure
56/// (dropped connection, camera reboot, ...) ends this source's thread with
57/// `Err`, the same way any other fatal [`SourceElement::run`] failure
58/// does, instead of looping forever inside `run()`. Reconnecting means
59/// building a fresh `RtspSource`/[`crate::pipeline::Pipeline`] — mirrors
60/// `Pipeline` itself not being reusable once it ends: watch
61/// [`crate::pipeline::Pipeline::bus`], and on error, call
62/// [`RtspSource::open`] again.
63///
64/// Uses `Packet::read` directly instead of `Input::packets()` — the
65/// latter silently retries forever inside its own `next()` on any non-EOF
66/// error (network timeout, connection reset, ...), which would make a
67/// stuck connection un-`Stop`-able (`drain_control` never gets a turn)
68/// and this element's "fail fast, don't retry" contract impossible to
69/// keep.
70pub struct RtspSource {
71 pp_log: PpLog,
72 name: Arc<str>,
73 input: ffmpeg::format::context::Input,
74 pads: Vec<SrcPad>,
75}
76
77impl RtspSource {
78 /// Connects to `url` (e.g. `rtsp://host:port/path`) and returns the
79 /// element alongside every stream the server advertised, so the
80 /// caller can inspect them before deciding which of `src_pads()` to
81 /// link — same pattern as `FileDemuxer::open`.
82 pub fn open(
83 name: impl Into<String>,
84 url: impl AsRef<str>,
85 options: RtspOptions,
86 ) -> std::result::Result<(Self, Vec<StreamInfo>), RtspSourceError> {
87 let mut dict = ffmpeg::Dictionary::new();
88 dict.set("rtsp_transport", options.transport.as_ffmpeg_option());
89 dict.set("timeout", &options.timeout.as_micros().to_string());
90
91 let input = ffmpeg::format::input_with_dictionary(url.as_ref(), dict)?;
92
93 let streams: Vec<StreamInfo> = input
94 .streams()
95 .map(|s| StreamInfo {
96 index: s.index(),
97 kind: s.parameters().medium(),
98 })
99 .collect();
100
101 let pads = streams
102 .iter()
103 .map(|s| SrcPad::new(format!("src_{}", s.index)))
104 .collect();
105
106 let name: Arc<str> = name.into().into();
107 let pp_log = element_pp_log(ElementType::RtspSource, &name, None);
108 pp_info!(
109 pp_log: &pp_log,
110 "opened: url={}, transport={:?}, {} stream(s)",
111 url.as_ref(),
112 options.transport,
113 streams.len()
114 );
115 Ok((
116 Self {
117 name,
118 pp_log,
119 input,
120 pads,
121 },
122 streams,
123 ))
124 }
125
126 /// Codec parameters for one of this stream's streams — what you need
127 /// to construct a matching [`crate::elements::SwDecoder`] for it.
128 pub fn stream_parameters(&self, index: usize) -> Option<ffmpeg::codec::Parameters> {
129 self.stream(index).map(|s| s.parameters())
130 }
131
132 /// The unit decoded frame timestamps for this stream are expressed in
133 /// — what you need to construct a matching [`crate::elements::Pacer`]
134 /// for it.
135 pub fn stream_time_base(&self, index: usize) -> Option<ffmpeg::Rational> {
136 self.stream(index).map(|s| s.time_base())
137 }
138
139 fn stream(&self, index: usize) -> Option<ffmpeg::format::stream::Stream<'_>> {
140 self.input.streams().find(|s| s.index() == index)
141 }
142}
143
144impl Element for RtspSource {
145 fn name(&self) -> Arc<str> {
146 self.name.clone()
147 }
148
149 fn element_type(&self) -> ElementType {
150 ElementType::RtspSource
151 }
152
153 fn pp_log(&self) -> &PpLog {
154 &self.pp_log
155 }
156
157 fn pp_log_mut(&mut self) -> &mut PpLog {
158 &mut self.pp_log
159 }
160}
161
162impl Source for RtspSource {
163 fn src_pads(&mut self) -> &mut [SrcPad] {
164 &mut self.pads
165 }
166}
167
168impl SourceElement for RtspSource {
169 fn run(&mut self, control: &ControlReceiver, bus: &Bus) -> Result<()> {
170 pp_info!(self, "started");
171 loop {
172 if drain_control(control, self, bus)?.stopped {
173 pp_info!(self, "stopped");
174 return Ok(());
175 }
176
177 let mut packet = ffmpeg::Packet::empty();
178 match packet.read(&mut self.input) {
179 Ok(()) => {
180 let index = packet.stream();
181 if let Some(pad) = self.pads.get_mut(index) {
182 // A downstream failure drops just this one packet
183 // — same "report, don't die" contract `Queue`'s
184 // worker gives a failing `Sink` — rather than
185 // ending this whole source thread over it.
186 if let Err(error) = pad.push(MediaBuffer::Packet(Arc::new(packet))) {
187 bus.post(
188 &self.pp_log,
189 BusEvent::Error {
190 element_type: ElementType::RtspSource,
191 name: self.name.clone(),
192 error,
193 },
194 );
195 }
196 }
197 }
198 // A real on-demand RTSP stream can send a clean EOF; a
199 // live camera essentially never will, but treat it the
200 // same way `FileDemuxer` treats running out of packets.
201 Err(ffmpeg::Error::Eof) => break,
202 // Anything else (connection reset, socket timeout, ...) is
203 // fatal — reported and this thread ends, rather than
204 // retried. See this type's own docs on why: retrying
205 // belongs to whoever's watching the bus, building a fresh
206 // `RtspSource` to reconnect with.
207 Err(error) => {
208 pp_error!(self, "read failed: {error}");
209 return Err(RtspSourceError::Ffmpeg(error).into());
210 }
211 }
212 }
213 for pad in self.pads.iter_mut() {
214 pad.push_eos(&self.pp_log)?;
215 }
216 pp_info!(self, "event=eos phase=source_completed outcome=ok");
217 Ok(())
218 }
219
220 fn seek(&mut self, _target: Duration) -> Result<Duration> {
221 Err(RtspSourceError::SeekUnsupported.into())
222 }
223}